# python内部类如int、float、str、dict、list、tuple、set等都是小写
# 自定义类需要采用大写开头，名称采用驼峰命名法
# 语法:
#      class 类名([父类]): # ([父类]) 可省略
#           # 公共属性可以通过实例或者类访问，实例.属性，class类名.属性
#           公共属性..
#           
#           # 特殊方法 
#           def __init__(self...):
#               pass
#
#           # 其他方法
#           def method_1(self...):
#               pass
 
# 相当于创建了个int对象a
a = int(10) # 等效 a = 10 

# 自定义类
class MyClass:
    pass # 占位

mc = MyClass()
print(MyClass, mc, isinstance(mc, MyClass)) # isinstance(instance, class) 判断某个实例是否是某个class的实例

mc2 = MyClass()
# 虽然class中没有该属性，但是可以赋值添加该属性
mc2.name = 'tom'
print(mc2.name)

class Person:
    __slots__ = ("name") # __slots__ 特殊变量可以定义该类只能有哪些属性，后面将不能通过动态增加属性，如person.age = 1报错
    # 类属性，此处的类属性就相当于java中的static类静态属性，所有实例共享
    # 可以通过Person的实例p.name访问，也可以通过Person.name访问
    name = 'anyone'
    # 方法, 类中定义的方法都会默认有一个参数，该参数就是对象实例的本身，由解析器自动传递给方法
    # self此处就是对象本身，约定取名self，但实际上该参数叫什么名都可以，如abc
    def hello(self):
        # 由于类方法中默认会传递一个参数，第一个参数默认的参数为对象本身，self就是对象本身
        # 注意：在类方法中无法访问类中定义的变量，如name，无法直接使用, 可以使用默认的参数self访问属性
        print('hello %s' %   self.name)

p = Person()
# 注意：虽然name是类属性，但是实例创建后，通过该形式相当于在p实例中增加name属性，而不是Person类中的属性的那个
# 所以此处修改name，Person.name不会发生变化，Person.name只能通过Person.name = xxx修改
# Person虽然是class类定义，但是Person也是一个对象，和Person实例p一样，都有一块自己的内存存放对象数据，
# 对实例p的属性或方法的修改只针对实例对象p，不会影响其他的对象，当删除实例对象p中的属性后，访问会再次从Person类中查找
# 否则如果实例对象p中有某个被访问的属性了，就不会向上查找类属性
p.name = 'aaaa' 
print(p.name, Person.name)
# 函数：直接通过 函数() 调用(如sorted())，对于函数调用，传递几个参数就是几个参数
# 方法：通过 对象.方法() 调用，方法默认会传递一个参数(默认传递的为第一个参数是调用对象实例的本身，相当于java中this), 
#   所以直接p.hello()报错：TypeError: hello() takes 0 positional arguments but 1 was given
p.hello()

# 对象属性和方法查找流程：(在类class中定义的叫做类属性和方法，所有实例共享，对于所有对象可见,类似于java中static类属性，
#                               对象实例独有的叫对象的属性和方法，仅自己可见实例属性，类似于java中的)
#      通过对象.属性或方法，首先会查找该对象实例中是否有该属性或方法，如果有直接返回
#      如果没有，则查找类中是否有该属性和方法，如果有返回，如果没有抛出异常
# 对于类定义：
#   如果某个属性或方法是所有实例共有的，则在类中定义
#   如果某个属性或方法对于每个对象独有的，则单独定义在每个对象实例中
#         所以一般情况下，方法为实例共有的定义保存在类中，属性定义保存到实例中
p.name = 'cat' # name每个实例不同，所以不需要在类中定义保存，此时由于实例p中有name属性，则直接使用实例中的，不会返回类中的name
print(p.name)
p.hello()
del p.name # 删除该属性，此时对象实例p中没有name属性，再次调用将返回类中的name属性
print(p.name)

# 特殊方法(魔术方法) 
# 在类中存在一类特殊方法， 以双下划线__开头，双下划线__结尾，如： __init__
class Person1:

    # 特殊方法，__init__ 特殊方法不需要自己调用
    # 该方法会在对象实例化时调用，__init__ 初始化方法
    # 此处实例化后，所有的新建实例都会有name和age属性，但是每个实例独享
    def __init__(self):
        self.name = 'tom'
        self.age = 10

    def hello(self):
        print('hello %s' % self.name)

p = Person1()
print(p.name, p.age)

class Person3:

    # 初始化方法除了可以初始化实例对象默认属性外，还可以在实例化时传递参数进行初始化
    # 传递的参数会依次从第二个参数开始赋值给此处的形参，相当于java中的有参构造
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def hello(self):
        print('hello %s' % self.name)
# 传递的参数分别会赋值给 __init__ 的形参
p = Person3('cat', 11)
print(p.name, p.age)
help(p)
print(p.__dict__)

# 无参构造：有参+默认值
class Monkey:
    # 设置了默认值得有参初始化特殊方法
    def __init__(self, name='Monkey'):
        self._name = name

m = Monkey()
print('Monkey', m)
# 其实实例对象的属性最终存储在__dict__中
class Dog: 
    '''
        dog class with optional args of construct
    '''
    def __init__(self, **keyword_args):
        # __dict__ 存储的是实例属性，也就是实例的属性都会存放在__dict__属性字典中
        self.__dict__ = keyword_args

    def say():
        '''
            dog say wang
        '''
        print('wang wang!')

d = Dog()
d = Dog(name='cat', age = 11)
print(d, d.name, d.age) # 属性存储在实例对象p的 __dict__ 属性中
help(Dog)
dir(Dog) # 查看类或者模块有哪些属性或方法

# 属性的访问安全性
# 通过get/set方法设置、获取实例属性名，外部无法知道实际属性名称
# 通过更改属性名来防止直接修改该属性，只能通过get/set方法修改属性值
# 防君子不妨小人，知道了实际名称一样可以修改属性值，python中的私有变量也是该形式实现的私有属性
class Dog1:
    def __init__(self, name) :
        self.hidden_name = name # 通过改变类属性名，防止直接通过 实例.name = xxx修改name名称
    def get_name(self):
        return self.hidden_name
    def set_name(self, name):
        self.hidden_name = name 

# 私有属性 双下划线 __属性名， 实际上该形式只是python帮我们改了属性名，
# python中双下划线表示的属性或方法为私有方法，python自动更改其名称，以使其对外不可见
# 会自动将__属性名 改为 __类名__属性名，与自己修改该属性名做隐藏效果一样
class Cat:
    
    def __init__(self, name):
        # 私有属性外部不可见，不能通过实例对象.属性获取，只能通过get/set方法
        self.__name = name

    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name

    def hello() :
        print('hello') 

c = Cat('cat')
# print(c, c.__name) # 报错，__name是私有属性，外部不可见，只能通过get_name获取
print(c, c.get_name())
c.__name = 'abc' # 这里修改的并不会修改该类中的__name
print(c.get_name(), c.__dict__) # 通过打印的__dict__可以看到其实内部属性名__name只是改为了__Cat__name
c._Cat__name = 'abc' # 直接修改python自动生成的属性名
print(c.get_name())
# 所以无法达到真正的属性私有，但是通常双下划线 __属性名 的形式不常使用，而采用单下划线 _属性名 的形式
# 所以，编程中，通常约定 _属性名 为私有属性，不要修改_属性名这种私有属性

# 可以直接使用约定的 私有属性 命名方式
# 属性装饰器
# 由于每次都是用get/set方法获取和修改属性很麻烦，所以python提供了属性装饰器注解
# @peoperty @属性名.setter 加在方法上面
class Cat1:
    def __init__(self, name):
        self._name = name

    # name()方法为get方法，可以直接通过 实例.name访问name属性
    # 属性可以只有get方法，为只读属性
    # 方法名称和实例变量不能相同，比如self.name作为属性变量就会导致死循环堆栈溢出，所以可以叫self._name
    @property
    def name(self):
        return self._name
    # name属性的set方法，名称与get相同，不过多一个值参数
    # 注意：set方法依赖get方法，也就是依赖@property装饰器方法，因为修改属性必须能访问才能修改
    #       有set必须有get，但是有get不需要有set
    @name.setter
    def name(self, name):
        self._name = name

c = Cat1('cat')
c.name = 'cde'
print(c.name)


# 继承
class Animal:
    # 特殊方法同样会被子类继承
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name
    
    @name.setter
    def name(self, name):
        self._name = name

    def eat(self):
        print('eat ..')

class Eagle(Animal):

    def fly(self):
        print('fly ..')

    # 重写父类方法
    def eat(self):
        print('eagle eat rabbit.')

e = Eagle('eagle') # 父类存在有参初始化构造函数，子类继承父类，所以也要传递初始化参数
e.name = 'eagle2'
e.eat()
e.fly()
# isinstance判断实例对象是否是类的实例，issubclass判断是否是某个类的子类
print(isinstance(e, Eagle), isinstance(e, Animal))
print(issubclass(Eagle, Animal), issubclass(Animal, object))
# super方法，重写特殊方法，并使用super特殊方法做初始化
class Fish(Animal):

    # 重写特殊方法
    def __init__(self, name, age):
        # super()就是父类对象，通过super()可以调用父类初始化方法, 相当于java中的super
        super().__init__(name)
        self._age = age

    @property
    def age(self):
        return self._age
    
f = Fish('shark', 100)
print(f.name, f.age)
# 查看某个类的所有父类
print(Fish.__bases__)
help(Fish)

# python中支持多重继承，可以有多个父类
# 虽然支持多重继承，但是一般情况下不要使用多重继承
# 多重继承，如果存在两个父类有相同的方法，则调用时，按照继承编写的顺序，执行前面那个父类中的方法
class Monster(Fish, Eagle):
    pass

m = Monster('monster', 1000)
m.eat()
m.fly()
print(m.name, m.age)

# 多态
# 多态最好的例子就是len(x)函数，该函数传递的参数数据类型不同但是都能达到计算长度的效果
# 原因就是对于x参数，只要该参数对象存在__len__特殊方法，就可以通过len计算长度，而不需要判断实际是什么类型
print(len([1,2,3]), len('abc')) # list和str都实现了__len__魔术方法

class A: 
    # 实现了该方法的都可以通过len函数调用
    def __len__(self):
        return 1

a = A()
print(len(a))
# 如自定义多态函数，不关心参数类型，只要参数有某个属性或者方法即可
def eat(obj):
    obj.eat() # 不关心obj实际类型，只要obj存在eat方法即可
# eat()函数就是多态的例子
eat(Animal('bird'))
eat(Fish('fish',10))

# 类属性和方法 使用注意
class C:
    # 类属性，可以通过实例对象获取到，也可以通过类对象获取
    count = 0

    def __init__(self, name):
        # 实例对象属性，只能通过实例对象访问(c.name), 不能通过类对象访问(A.name)
        self._name = name

    @property
    def name(self):
        return self._name

    # 实例方法：
    # 1. 在类中定义，并且需要指定默认参数，即第一个参数为self的方法
    # 2. 实例方法可以通过实例对象调用，也可以通过类对象调用
    #    当通过实例对象调用时，解析器会自动传递第一个参数实例对象本身
    #    当通过类对象调用时，需要手动传递self参数
    def hi(self):
        print('hi')

    # 类方法
    #   在类内部定义的方法，并使用@classmethod来修饰的方法就是类方法
    #   类方法需要指定默认参数，即第一个参数是类对象本身，一般取名cls
    #   类方法可以通过类对象调用也可以通过实例对象调用
    @classmethod
    def hi2(cls):
        print('hi2', cls)

    # 静态方法
    #   静态方法在类内部定义并且使用@staticmethod修饰的方法
    #   静态方法不需要指定任何默认参数
    #   静态方法既可以通过类对象调用也可以通过实例对象调用
    @staticmethod
    def hi3():
        print('hi3')

c = C('c')
print(c.name)
# 下面两个实例方法的调用等价
c.hi()  # 默认解析器会传递实例对象本身给hi方法作为第一个参数
C.hi(c) # 等价c.hi()， 需要手动传递实例对象作为参数
# 下面这两个类方法调用等价
c.hi2() # 默认会传递实例对象的类型作为第一个参数
C.hi2() # 默认会传递类对象本身作为第一个参数
# 下面两个类静态方法调用等价
c.hi3()
C.hi3()



# 垃圾回收
# python中对于没用的对象(没有被引用的一块对象所在的内存)或者程序退出，python垃圾回收器会自动回收内存，不需要干预
class D:
    def __init__(self):
        pass

    # 特殊方法，会在实例对象被删除前调用
    def __del__(self):
        print('delete', self)

d = D()
print(d) # 此时对象没有被删除
# d = None # 此时对象内存也会被删除，因为没有引用了
# del d # 与d = None效果相同，此时删除d，则d指向的内存也没有引用了，也会被删除
# input('press any key continue.') # 为对象被删除效果，阻止程序退出


# 类的特殊方法：所有特殊方法都是以双下划线开头和结尾的方法，如 __init__
# 特殊方法一般不需要手动调用
class E:
    '''
        special method
    '''
    def __init__(self):
        self.name = 'abc'

    # 特殊方法，可以通过内建函数repr(e)来产生一些信息，用于调试
    # 主要在交互模式中使用的，交互模式中，对象不通过print打印，调用的是该方法
    # 如：交互模式种 a = 'hello' 然后回车再输入a再回车，此时调用的就是repr，如果通过print则调用__str__
    def __repr__(self):
        # 必须返回一个字符串类型的数据
        return 'repr name => ' + self.name

    # 特殊方法，可以通过内建函数format() print()打印对象信息
    # 相当于java中的toString， 用于组建一个可以代表当前对象的字符串信息
    def __str__(self):
        # 返回一个可以代表当前对象的字符串信息
        return 'str name => ' + self.name

    # 此外还有很多特殊方法，下面这些，重写以后可以实现对象间的比较
    # object.__lt__(self, other) < 第一个参数是a>b中的a,other是b, 返回bool
    # object.__le__(self, other) <=
    # object.__eq__(self, other) ==
    # object.__ne__(self, other) !=
    # object.__gt__(self, other) >
    # object.__ge__(self, other) >=

    # object.__hash__(self) # 对象的hash值 返回数值
    # object.__bool__(self) # 对象的bool值，做bool转换或者判断时 返回bool
    # object.__bytes__(self) # 对象的字节码

    # 算数运算
    # object.__add__(self, other) 
    # object.__sub__(self, other) 
    # object.__mul__(self, other) 
    # object.__matmul__(self, other) 
    # object.__truediv__(self, other) 
    # object.__floordiv__(self, other) 
    # object.__mod__(self, other) 
    # object.__divmod__(self, other) 
    # object.__pow__(self, other[, modulo]) 
    # object.__lshift__(self, other) 
    # object.__rshift__(self, other) 
    # object.__and__(self, other) 
    # object.__xor__(self, other) 
    # object.__or__(self, other) 


    # 更多查看文档

e = E()
# 通过重写特殊方法 __repr__ 并且没重写 __str__ ，通过print(e)与repr(e)产生的效果相同
# 如果重写str，则print()会调用__str__特殊方法
print(e, repr(e))


